﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

using Microsoft.Office.Interop.Excel;
using DataTable = System.Data.DataTable;
using ExcelApplication = Microsoft.Office.Interop.Excel.Application;


namespace Applib.OfficeInterop
{


    public sealed class ExcelWriter : IDisposable
    {
        //excel instances
        ExcelApplication excel = null;
        Workbook book = null;
        Worksheet currentsheet = null;

        //readonly / const values
        private string excelFile = string.Empty;
        private static readonly string emptySheetName = "empty-sheet-name";
        private const string HEADNAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        private DataSet rawDataSet = null;
        private DataTable currentTable = null;

        private DataTable columnNameTable = new DataTable();

        /// <summary>
        /// stored data as same as in excel.
        /// </summary>
        public DataSet RawDataSet { get { return rawDataSet; } }

        /// <summary>
        /// map current column name to its index in datatable
        /// </summary>
        Dictionary<string, int> captionToExcelIndexMap = null;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="xlsxFile"></param>
        /// <exception cref="NotSupportedException"/>
        public void Initialize(string xlsxFile)
        {
            try
            {
                InternalCleanup();
                InternalInitializeRawDataContainers();

                this.excelFile = Path.GetFullPath(xlsxFile);
                //suppose the office is an english version.
                System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-us");

                excel = new ExcelApplication();
                excel.DisplayAlerts = false;

                //always overwrite existing file
                book = (Workbook)excel.Workbooks.Add(Type.Missing);
                for (int i = 1; i <= book.Worksheets.Count; i++)
                {
                    ((Worksheet)book.Worksheets[i]).Delete();
                }


                //create file
                book.SaveAs(excelFile, XlFileFormat.xlOpenXMLWorkbook,
                            Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                            XlSaveAsAccessMode.xlExclusive,
                            Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);

                currentsheet = (Worksheet)book.Worksheets.get_Item(1);
                currentsheet.Name = emptySheetName;
                DestroyComObject(currentsheet);
                book.Save();
            }
            catch (Exception e)
            {
                throw new IOException("office error occurs." + e.Message + "\r\n" + excelFile);
            }
        }



        public void CreateSheet(string sheetName, Type rowType)
        {
            CreateSheet(sheetName, rowType, false, true);
        }

        /// <summary>
        /// Create empty sheet, or fill defined column names.
        /// </summary>
        /// <param name="sheetName"></param>
        /// <param name="rowType"></param>
        /// <param name="insertAsCurrentHeader"></param>
        /// <param name="addToRawTable"></param>
        private void CreateSheet(string sheetName, Type rowType, bool insertAsCurrentHeader, bool addToRawTable)
        {
            if (this.excel == null)
                throw new InvalidOperationException("Initialize application first.");

            DestroyComObject(currentsheet);

            if (book.Worksheets.Count == 1 &&
                (currentsheet = (Worksheet)book.Worksheets.get_Item(1)).Name == emptySheetName)
            {
                currentsheet.Name = sheetName;
            }
            else
            {
                Worksheet tempsheet = null;
                if (insertAsCurrentHeader) //for summary, insert as first sheet
                {
                    tempsheet = (Worksheet)book.Worksheets.get_Item(1);
                    currentsheet = (Worksheet)book.Worksheets.Add(tempsheet, Type.Missing, Type.Missing, Type.Missing);
                }
                else
                {
                    tempsheet = (Worksheet)book.Worksheets.get_Item(book.Worksheets.Count);
                    currentsheet = (Worksheet)book.Worksheets.Add(Type.Missing, tempsheet, Type.Missing, Type.Missing);

                }
                currentsheet.Name = sheetName;

                DestroyComObject(tempsheet);
            }


            if (!addToRawTable) //will not fill any cell with data or column name
                return;

            currentTable = new DataTable(sheetName);
            rawDataSet.Tables.Add(currentTable);


            InitRowColumnNames(rowType);


            int ci = 0;
            foreach (var key in captionToExcelIndexMap)
            {
                string caption = key.Key;
                currentsheet.Cells[1, ++ci] = caption;
            }
            //set column row color
            currentsheet.Range[string.Format("A1:{0}1", HEADNAME[ci - 1])].Interior.Color = System.Drawing.ColorTranslator.ToOle(Color.LightSeaGreen);


            foreach (Window win in excel.Windows)
            {
                ((Range)currentsheet.Range["B2"]).Select(); //for freezing first row and column
                win.FreezePanes = true;
                ((Range)currentsheet.Range["A1"]).Select();
                DestroyComObject(win);
            }

        }

        public void WriteRow<T>(T row) where T : class
        {
            if (this.excel == null)
                throw new InvalidOperationException("Initialize application first.");

            //if excel is not closed correctly, sometime unable to create a new excel until a restart.
            if (currentTable == null)
                return;
            //throw new InvalidOperationException("Need create sheet before writing data.");


            DataRow dtRow = currentTable.NewRow();
            currentTable.Rows.Add(dtRow);

            var fields = row.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);

            int ridx = currentTable.Rows.Count + 1;

            foreach (FieldInfo f in fields)
            {
                #region fill data with row
                var dataAttrib = f.GetCustomAttributes(typeof(DataInfoAttribute), true);
                var formAtattrib = f.GetCustomAttributes(typeof(HighLightConditionAttribute), true);
                if (dataAttrib.Length <= 0)
                    continue;

                DataInfoAttribute dataInfo = (DataInfoAttribute)dataAttrib[0];

                int cidx = 0;
                try
                {
                    cidx = captionToExcelIndexMap[dataInfo.ColumnName];
                }
                catch (Exception)
                {
                    throw new InvalidOperationException("Given name is not defined; Refer to CreateSheet(). Invalid column name is " + dataInfo.ColumnName);
                }

                object val = f.GetValue(row);
                string cellValue = val == null ? string.Empty : val.ToString();
                currentsheet.Cells[ridx, cidx] = cellValue;

                //put result in statistic table
                if (currentTable.Columns.Contains(dataInfo.ColumnName))
                    dtRow[dataInfo.ColumnName] = cellValue;

                #endregion

                #region format cells
                Range column = (Range)currentsheet.Columns[HEADNAME[cidx - 1].ToString()];
                column.AutoFit();

                float columnWidth = float.Parse(column.ColumnWidth.ToString());
                if (columnWidth > 70) column.ColumnWidth = 70;

                ((Range)currentsheet.Range[string.Format("A1:{0}{1}", HEADNAME[captionToExcelIndexMap.Count], ridx + 1)]).Borders[XlBordersIndex.xlInsideHorizontal].LineStyle = 1;
                ((Range)currentsheet.Range[string.Format("A1:{0}{1}", HEADNAME[captionToExcelIndexMap.Count], ridx)]).Borders[XlBordersIndex.xlInsideVertical].LineStyle = 1;
                currentsheet.Range[string.Format("A1:{0}{1}", HEADNAME[captionToExcelIndexMap.Count], ridx + 1)].HorizontalAlignment = XlHAlign.xlHAlignLeft;
                if (formAtattrib.Length <= 0 || string.IsNullOrEmpty(cellValue))
                    continue;


                HighLightConditionAttribute formatInfo = (HighLightConditionAttribute)formAtattrib[0];

                bool shouldHighLight = false;

                try
                {
                    switch (formatInfo.Condition)
                    {
                        case HighLighConditions.RegexMatch:
                            shouldHighLight = System.Text.RegularExpressions.Regex.Match(cellValue, formatInfo.Pattern).Success;
                            break;


                        case HighLighConditions.Contains:
                        case HighLighConditions.EndsWith:
                        case HighLighConditions.Equals:
                        case HighLighConditions.StartsWith:
                            //compare that using case insensitive 
                            string tmpCell = cellValue.ToLower();
                            string tmpPatten = formatInfo.Pattern.ToLower();
                            MethodInfo md = tmpCell.GetType().GetMethod(formatInfo.Condition.ToString(), new Type[] { typeof(string) });
                            shouldHighLight = (bool)md.Invoke(tmpCell, new object[1] { tmpPatten });

                            break;

                        default:
                            throw new NotImplementedException(formatInfo.Condition.ToString());

                    }

                }
                catch (Exception)
                {
                    //to avoid empty value
                }

                if (shouldHighLight)
                {
                    //column idx - 1, excel cell to array
                    //row idx + 1 , has column
                    string rg = string.Format("{0}{1}:{0}{1}", HEADNAME[cidx - 1], currentTable.Rows.Count + 1);
                    currentsheet.Range[rg].Interior.Color = formatInfo.HighLightArgb;
                }

                #endregion
            }


            book.Save();

        }

        public void WriteSummary()
        {
            //rawDataSet.ReadXml("1.xml");
            //for (int imb = 0; imb < 10; imb++)
            //{
            //    DataTable dt = rawDataSet.Tables[0].Copy();
            //    dt.TableName = imb + "test";
            //    rawDataSet.Tables.Add(dt);
            //}

            if (rawDataSet.Tables.Count == 0)
                return;

            const string summaryTableName = "Summary";
            CreateSheet(summaryTableName, null, true, false);


            int x = 2, y = 1;

            int bottomRightX = x, topLeftY = y;

            foreach (DataTable tb in rawDataSet.Tables)
            {
                //starts with sheet name, move to next row
                currentsheet.Cells[y++, 1] = tb.TableName;
                string rg = string.Format("A{0}:K{0}", y - 1);
                currentsheet.Range[rg].Interior.Color = HighLightColors.DarkBlue;
                currentsheet.Range[rg].Font.Color = HighLightColors.White;
                topLeftY = y;

                //caption refers to InternalInitializeRawDataContainers()
                var rowNames = columnNameTable.Select(string.Format("tableName = '{0}'", tb.TableName));

                foreach (var c in rowNames)
                {
                    string columnName = c[1].ToString();
                    var rows = tb.DefaultView.ToTable(true, columnName).Rows;

                    if (rows.Count == 0) //if not data dont write any log anymore
                        continue;

                    //vertical: old column name, move right one cell
                    currentsheet.Cells[y, x++] = columnName;

                    foreach (DataRow r in rows)
                    {
                        object val = r[columnName];

                        if (val == null || string.IsNullOrEmpty(val.ToString()))
                            continue;

                        //horizontal, values,and its total count 
                        currentsheet.Cells[y, x] = val.ToString();
                        currentsheet.Cells[y + 1, x] = tb.Select(string.Format("[{0}] = '{1}'", columnName, val)).Length;

                        x++;//move right one cell
                        if (bottomRightX < x)
                            bottomRightX = x;
                    }


                    x = 2;
                    y += 2;// move 2 everytime then focus to new position

                }

                //table area: borderX - 2 means last column has empty data, and array starts from zero
                //y - 1 means last row has empty data
                //as table always has columns (defined outside and verified when create sheets), don't have to run skip.

                bottomRightX = bottomRightX - 2;
                //when there's no data dont set offset set to beginning
                bottomRightX = bottomRightX < 2 ? 2 : bottomRightX;

                rg = string.Format("B{0}:{1}{2}", topLeftY, HEADNAME[bottomRightX], y - 1);
                currentsheet.Range[rg].Borders[XlBordersIndex.xlInsideHorizontal].LineStyle = 1;
                currentsheet.Range[rg].Borders[XlBordersIndex.xlInsideVertical].LineStyle = 1;
                currentsheet.Range[rg].Borders[XlBordersIndex.xlEdgeTop].LineStyle = 1;
                currentsheet.Range[rg].Borders[XlBordersIndex.xlEdgeBottom].LineStyle = 1;
                currentsheet.Range[rg].Borders[XlBordersIndex.xlEdgeRight].LineStyle = 1;
                currentsheet.Range[rg].Borders[XlBordersIndex.xlEdgeLeft].LineStyle = 1;

                currentsheet.Range[rg].HorizontalAlignment = XlHAlign.xlHAlignRight;


                //highlight columns and leave value as default
                for (int tmp = topLeftY; tmp < y; )
                {
                    rg = string.Format("B{0}:{1}{0}", tmp, HEADNAME[bottomRightX]);
                    currentsheet.Range[rg].Interior.Color = HighLightColors.LightSkyBlue;
                    tmp += 2;
                }

            }

            //rawDataSet.WriteXml("1.xml");
            foreach (var c in HEADNAME)
            {
                Range range = ((Range)currentsheet.Columns[c.ToString()]);
                range.AutoFit();

                //if column is too long or too short it looks ugly
                float columnWidth = float.Parse(range.ColumnWidth.ToString());
                if (columnWidth > 100) range.ColumnWidth = 100;

                if (columnWidth < 10)
                    range.ColumnWidth = (object)10;

            }

        }


        public void Close()
        {
            if (closed) return;
            closed = true;
            try
            {
                if (book == null || excel == null)
                    return;


                Microsoft.Office.Interop.Excel._Worksheet firstSheet = (Worksheet)book.Worksheets.get_Item(1);
                firstSheet.Activate();
                DestroyComObject(firstSheet);

                book.Save();

                excel.Quit();
                //occasionally we see 'access denied' issue, the cause may be last process is still running but we kill that.
                //need wait here so new process can start without issue.
                System.Threading.Thread.Sleep(1000);
            }
            catch (Exception)
            {
            }


        }



        private void InternalInitializeRawDataContainers()
        {
            rawDataSet = new DataSet("alldata");

            columnNameTable.Columns.Add("tableName", typeof(string));
            columnNameTable.Columns.Add("ColumnName", typeof(string));
        }

        [System.Diagnostics.Conditional("DEBUG")]
        private void InternalCleanup()
        {
            //if file is opened by user and excel automation is unable to open/write that file
            foreach (var p in System.Diagnostics.Process.GetProcessesByName("excel"))
            {
                p.Kill();
            }
            //occasionally we see 'access denied' issue, the cause may be last process is still running but we kill that.
            //need wait here so new process can start without issue.
            System.Threading.Thread.Sleep(1000);


        }

        private static void DestroyComObject(object o)
        {
            if (o != null)
            {
                try
                {
                    Marshal.ReleaseComObject(o);
                }
                catch (Exception) { }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="t"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        private void InitRowColumnNames(Type t)
        {
            if (captionToExcelIndexMap == null)
                captionToExcelIndexMap = new Dictionary<string, int>();
            else
                captionToExcelIndexMap.Clear();

            var fields = t.GetFields(BindingFlags.Public | BindingFlags.Instance);
            string[,] names = new string[fields.Length, 2];
            string[,] noIdxDefinedNames = new string[fields.Length, 2];
            int noidxPos = 0;
            int posMax = 0;


            foreach (FieldInfo f in fields)
            {
                var attrib = f.GetCustomAttributes(typeof(DataInfoAttribute), true);
                if (attrib.Length <= 0)
                    continue;

                DataInfoAttribute info = (DataInfoAttribute)attrib[0];
                try
                {
                    if (info.ColumnIndex == uint.MaxValue)
                    {
                        noIdxDefinedNames[noidxPos, 0] = info.ColumnName;
                        noIdxDefinedNames[noidxPos, 1] = info.IsStatistic.ToString();
                        noidxPos++;
                    }
                    else
                    {
                        if (!string.IsNullOrEmpty(names[info.ColumnIndex, 0]))
                        {
                            throw new InvalidOperationException("A column has same index is already defined, index equals " + info.ColumnIndex);
                        }

                        names[info.ColumnIndex, 0] = info.ColumnName;
                        names[info.ColumnIndex, 1] = info.IsStatistic.ToString();
                        if (posMax < info.ColumnIndex)
                        {
                            posMax = (int)info.ColumnIndex;
                        }
                    }
                }
                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException("Index number of column (" + info.ColumnName + ") is too large.");
                }
            }

            int destStartIdx = (posMax + 1) * names.GetLength(1); //start with next position
            int length = noidxPos * noIdxDefinedNames.GetLength(1);




            Array.Copy(noIdxDefinedNames, 0, names, destStartIdx, length);

            for (int idx = 0; idx < names.GetLength(0); idx++)
            {
                string tempname = names[idx, 0];
                bool isStatistic = names[idx, 1] == "True";
                if (string.IsNullOrEmpty(tempname))
                    throw new InvalidOperationException("Column index must be continuous, a column with index equals " + idx + " should be defined.");

                if (captionToExcelIndexMap.ContainsKey(tempname))
                    throw new InvalidOperationException("A same column has been defined already, the name is " + tempname);
                captionToExcelIndexMap.Add(tempname, idx + 1); //exel cell pos starts with 1


                currentTable.Columns.Add(new DataColumn(tempname));

                if (isStatistic)
                {
                    DataRow tmpRow = columnNameTable.NewRow();
                    tmpRow[0] = currentTable.TableName;
                    tmpRow[1] = tempname;

                    columnNameTable.Rows.Add(tmpRow);
                }
            }

            if (captionToExcelIndexMap.Count == 0)
                throw new InvalidOperationException("Defined row doesn't have valid columns.");

        }




        #region IDisposable
        bool disposed = false;
        bool closed = false;
        ~ExcelWriter()
        {
            this.Dispose();
        }

        public void Dispose()
        {
            if (!disposed)
            {
                if (currentTable != null)
                {
                    currentTable.Dispose();
                    currentTable = null;
                }
                if (rawDataSet != null)
                {
                    rawDataSet.Dispose();
                    rawDataSet = null;
                }
                Close();

                DestroyComObject(currentsheet);
                DestroyComObject(book);
                DestroyComObject(excel);

                currentsheet = null;
                book = null;
                excel = null;
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }

            disposed = true;
            GC.SuppressFinalize(this);



        }

        #endregion
    }


    #region attribute definations
    [AttributeUsage(AttributeTargets.Field)]
    public sealed class HighLightConditionAttribute : Attribute
    {
        public string Pattern;
        public HighLighConditions Condition = HighLighConditions.None;
        public HighLightColors HighLightArgb = HighLightColors.Red;
    }

    public enum HighLighConditions
    {
        Equals,
        StartsWith,
        EndsWith,
        Contains,
        RegexMatch,

        None
    }
    [AttributeUsage(AttributeTargets.Field)]
    public sealed class DataInfoAttribute : Attribute
    {
        /// <summary>
        /// Column name.
        /// </summary>
        public string ColumnName = string.Empty;
        /// <summary>
        /// Column index, if this is not assigned, it will be uint.MaxValue,
        /// represents this will appear after those columns defined with an index.
        /// </summary>
        public uint ColumnIndex = uint.MaxValue;


        /// <summary>
        /// Default value.
        /// </summary>
        public string DefaultValue = string.Empty;

        /// <summary>
        /// Statistic summary will count this column if this value is set to 'true'
        /// </summary>
        public bool IsStatistic = false;
    }



    public enum HighLightColors : uint
    {
        Transparent = 0x00FFFFFF,
        AliceBlue = 0x00FFF8F0,
        AntiqueWhite = 0x00D7EBFA,
        Aqua = 0x00FFFF00,
        Aquamarine = 0x00D4FF7F,
        Azure = 0x00FFFFF0,
        Beige = 0x00DCF5F5,
        Bisque = 0x00C4E4FF,
        Black = 0x00000000,
        BlanchedAlmond = 0x00CDEBFF,
        Blue = 0x00FF0000,
        BlueViolet = 0x00E22B8A,
        Brown = 0x002A2AA5,
        BurlyWood = 0x0087B8DE,
        CadetBlue = 0x00A09E5F,
        Chartreuse = 0x0000FF7F,
        Chocolate = 0x001E69D2,
        Coral = 0x00507FFF,
        CornflowerBlue = 0x00ED9564,
        Cornsilk = 0x00DCF8FF,
        Crimson = 0x003C14DC,
        Cyan = 0x00FFFF00,
        DarkBlue = 0x008B0000,
        DarkCyan = 0x008B8B00,
        DarkGoldenrod = 0x000B86B8,
        DarkGray = 0x00A9A9A9,
        DarkGreen = 0x00006400,
        DarkKhaki = 0x006BB7BD,
        DarkMagenta = 0x008B008B,
        DarkOliveGreen = 0x002F6B55,
        DarkOrange = 0x00008CFF,
        DarkOrchid = 0x00CC3299,
        DarkRed = 0x0000008B,
        DarkSalmon = 0x007A96E9,
        DarkSeaGreen = 0x008BBC8F,
        DarkSlateBlue = 0x008B3D48,
        DarkSlateGray = 0x004F4F2F,
        DarkTurquoise = 0x00D1CE00,
        DarkViolet = 0x00D30094,
        DeepPink = 0x009314FF,
        DeepSkyBlue = 0x00FFBF00,
        DimGray = 0x00696969,
        DodgerBlue = 0x00FF901E,
        Firebrick = 0x002222B2,
        FloralWhite = 0x00F0FAFF,
        ForestGreen = 0x00228B22,
        Fuchsia = 0x00FF00FF,
        Gainsboro = 0x00DCDCDC,
        GhostWhite = 0x00FFF8F8,
        Gold = 0x0000D7FF,
        Goldenrod = 0x0020A5DA,
        Gray = 0x00808080,
        Green = 0x00008000,
        GreenYellow = 0x002FFFAD,
        Honeydew = 0x00F0FFF0,
        HotPink = 0x00B469FF,
        IndianRed = 0x005C5CCD,
        Indigo = 0x0082004B,
        Ivory = 0x00F0FFFF,
        Khaki = 0x008CE6F0,
        Lavender = 0x00FAE6E6,
        LavenderBlush = 0x00F5F0FF,
        LawnGreen = 0x0000FC7C,
        LemonChiffon = 0x00CDFAFF,
        LightBlue = 0x00E6D8AD,
        LightCoral = 0x008080F0,
        LightCyan = 0x00FFFFE0,
        LightGoldenrodYellow = 0x00D2FAFA,
        LightGreen = 0x0090EE90,
        LightGray = 0x00D3D3D3,
        LightPink = 0x00C1B6FF,
        LightSalmon = 0x007AA0FF,
        LightSeaGreen = 0x00AAB220,
        LightSkyBlue = 0x00FACE87,
        LightSlateGray = 0x00998877,
        LightSteelBlue = 0x00DEC4B0,
        LightYellow = 0x00E0FFFF,
        Lime = 0x0000FF00,
        LimeGreen = 0x0032CD32,
        Linen = 0x00E6F0FA,
        Magenta = 0x00FF00FF,
        Maroon = 0x00000080,
        MediumAquamarine = 0x00AACD66,
        MediumBlue = 0x00CD0000,
        MediumOrchid = 0x00D355BA,
        MediumPurple = 0x00DB7093,
        MediumSeaGreen = 0x0071B33C,
        MediumSlateBlue = 0x00EE687B,
        MediumSpringGreen = 0x009AFA00,
        MediumTurquoise = 0x00CCD148,
        MediumVioletRed = 0x008515C7,
        MidnightBlue = 0x00701919,
        MintCream = 0x00FAFFF5,
        MistyRose = 0x00E1E4FF,
        Moccasin = 0x00B5E4FF,
        NavajoWhite = 0x00ADDEFF,
        Navy = 0x00800000,
        OldLace = 0x00E6F5FD,
        Olive = 0x00008080,
        OliveDrab = 0x00238E6B,
        Orange = 0x0000A5FF,
        OrangeRed = 0x000045FF,
        Orchid = 0x00D670DA,
        PaleGoldenrod = 0x00AAE8EE,
        PaleGreen = 0x0098FB98,
        PaleTurquoise = 0x00EEEEAF,
        PaleVioletRed = 0x009370DB,
        PapayaWhip = 0x00D5EFFF,
        PeachPuff = 0x00B9DAFF,
        Peru = 0x003F85CD,
        Pink = 0x00CBC0FF,
        Plum = 0x00DDA0DD,
        PowderBlue = 0x00E6E0B0,
        Purple = 0x00800080,
        Red = 0x000000FF,
        RosyBrown = 0x008F8FBC,
        RoyalBlue = 0x00E16941,
        SaddleBrown = 0x0013458B,
        Salmon = 0x007280FA,
        SandyBrown = 0x0060A4F4,
        SeaGreen = 0x00578B2E,
        SeaShell = 0x00EEF5FF,
        Sienna = 0x002D52A0,
        Silver = 0x00C0C0C0,
        SkyBlue = 0x00EBCE87,
        SlateBlue = 0x00CD5A6A,
        SlateGray = 0x00908070,
        Snow = 0x00FAFAFF,
        SpringGreen = 0x007FFF00,
        SteelBlue = 0x00B48246,
        Tan = 0x008CB4D2,
        Teal = 0x00808000,
        Thistle = 0x00D8BFD8,
        Tomato = 0x004763FF,
        Turquoise = 0x00D0E040,
        Violet = 0x00EE82EE,
        Wheat = 0x00B3DEF5,
        White = 0x00FFFFFF,
        WhiteSmoke = 0x00F5F5F5,
        Yellow = 0x0000FFFF,
        YellowGreen = 0x0032CD9A,
    }
    #endregion
}


